/*
The MIT License (MIT)

Copyright (c) 2013 Mike Dapiran, Brian May, Richard Pospesel, and Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "hhdString.h"

#include "hhdFloat.h"
#include "hhdDouble.h"

#include "hhdIO.h"

#include "hhdArray1D.hpp"
#include "hhdIList.hpp"
#include "hhdPair.hpp"

#include "hhdAssert.h"

#include <cstring>
#include <cstdio>

//#include "tinyxml.h"
#include "hhdStringBuilder.h"
#include "hhdArrayList.hpp"
// gets rid of the sprintf warnings, there won't be any problems with that here
#pragma warning( push )
#pragma warning(disable : 4996)

namespace hhd
{
    String::String()
    {
        _length = 0;
        _string_data = new char8_t[1];
        _string_data[0] = '\0';
    }
    String::~String()
    {
        delete[] _string_data;
    }

    String::String(const String& in_other)
    {
        _length = in_other._length;
        _string_data = new char8_t[_length + 1];
        if(_length != 0)
        {
            memcpy(_string_data, in_other._string_data, _length);
        }
        _string_data[_length] = 0;

    }
    String::String(const char8_t* in_char_array)
    {
        if(in_char_array == NULL)
        {
            _length = 0;
            _string_data = new char8_t[1];
            _string_data[0] = '\0';
        }
        else
        {
            _length = strlen(in_char_array);
            _string_data = new char8_t[_length + 1];
            if(_length != 0)
            {
                memcpy(_string_data, in_char_array, _length);
            }
            _string_data[_length] = 0;
        }
    }

    String::String(const char8_t* in_char_array, size_t in_offset, size_t in_length)
    {
        _length = in_length;
        _string_data = new char8_t[_length + 1];
        char8_t* other = (char8_t*)(in_char_array +in_offset);
        memcpy(_string_data, other, _length);
        _string_data[_length] = 0;
    }

    String::String(char in_char)
    {
        _length = 1;
        _string_data = new char8_t[2];
        _string_data[0] = in_char;
        _string_data[1] = 0;
    }

    String::String(uchar8_t in_char)
    {
        _length = 1;
        _string_data = new char8_t[2];
        _string_data[0] = (char8_t)in_char;
        _string_data[1] = 0;
    }

    String::String(bool in_bool)
    {
        if(in_bool)
        {
            _length = 4;
            _string_data = new char8_t[5];
            _string_data[0] = 'T';
            _string_data[1] = 'r';
            _string_data[2] = 'u';
            _string_data[3] = 'e';
            _string_data[4] = 0;
        }
        else
        {
            _length = 5;
            _string_data = new char8_t[6];
            _string_data[0] = 'F';
            _string_data[1] = 'a';
            _string_data[2] = 'l';
            _string_data[3] = 's';
            _string_data[4] = 'e';
            _string_data[5] = 0;
        }
    }

    String::String(int32_t in_int)
    {
        // longest int: -2147483648; 10 digits + sign + 0 == 12
        _string_data = new char8_t[12];
        _length = sprintf(_string_data, "%d", in_int);
    }
    String::String(uint32_t in_uint)
    {
        // longest int: 4294967295; 10 digits + 0 == 11
        _string_data = new char8_t[11];
        _length = sprintf(_string_data, "%u", in_uint);
    }
    String::String(int64_t in_long_long)
    {
        // longest long long: -9.22337204 × 10^18; 18 digits + sign + 0 == 20
        _string_data = new char8_t[20];
        _length = sprintf(_string_data, "%" HHD_D64, in_long_long);
    }
    String::String(uint64_t in_ulong_long)
    {
        // longest unsigned long long: 1.84467441 × 10^19; 19 digits + 0 == 20
        _string_data = new char8_t[20];
        _length = sprintf(_string_data, "%" HHD_U64, in_ulong_long);
    }
    String::String(float in_float)
    {
        if(in_float != in_float)	// nan
        {
            _string_data = new char8_t[4];
            _string_data[0] = 'N';
            _string_data[1] = 'a';
            _string_data[2] = 'N';
            _string_data[3] = 0;
            _length = 3;
            return;
        }
        else if(in_float == Float::Infinity)
        {
            _string_data = new char8_t[9];
            _string_data[0] = 'I';
            _string_data[1] = 'n';
            _string_data[2] = 'f';
            _string_data[3] = 'i';
            _string_data[4] = 'n';
            _string_data[5] = 'i';
            _string_data[6] = 't';
            _string_data[7] = 'y';
            _string_data[8] = 0;
            _length = 8;
            return;
        }
        else if(in_float == Float::NegativeInfinity)
        {
            _string_data = new char8_t[10];
            _string_data[0] = '-';
            _string_data[1] = 'I';
            _string_data[2] = 'n';
            _string_data[3] = 'f';
            _string_data[4] = 'i';
            _string_data[5] = 'n';
            _string_data[6] = 'i';
            _string_data[7] = 't';
            _string_data[8] = 'y';
            _string_data[9] = 0;
            _length = 9;
            return;
        }

        // 32 bit floating point supports up to 7 digits of precision, and an exponent from -90 to 90
        // so sign + 7 digits + decimal + e + sign + 2 exponent digits + 0 == 14
        _string_data = new char8_t[14];
        _length = sprintf(_string_data, "%.6g", in_float);
    }

    String::String(double in_double)
    {
        if(in_double != in_double)	// nan
        {
            _string_data = new char8_t[4];
            _string_data[0] = 'N';
            _string_data[1] = 'a';
            _string_data[2] = 'N';
            _string_data[3] = 0;
            _length = 3;
            return;
        }
        else if(in_double == Double::Infinity)
        {
            _string_data = new char8_t[9];
            _string_data[0] = 'I';
            _string_data[1] = 'n';
            _string_data[2] = 'f';
            _string_data[3] = 'i';
            _string_data[4] = 'n';
            _string_data[5] = 'i';
            _string_data[6] = 't';
            _string_data[7] = 'y';
            _string_data[8] = 0;
            _length = 8;
            return;
        }
        else if(in_double == Double::NegativeInfinity)
        {
            _string_data = new char8_t[10];
            _string_data[0] = '-';
            _string_data[1] = 'I';
            _string_data[2] = 'n';
            _string_data[3] = 'f';
            _string_data[4] = 'i';
            _string_data[5] = 'n';
            _string_data[6] = 'i';
            _string_data[7] = 't';
            _string_data[8] = 'y';
            _string_data[9] = 0;
            _length = 9;
            return;
        }

        // 64 bit floating point supports up to 16 digits of precision, and an exponent from -1022 to 1023
        // so sign + 16 digits + decimal + e + sign + 4 exponent digits + 0 == 25
        _string_data = new char8_t[25];
        _length = sprintf(_string_data, "%.15g", in_double);
    }

    String::String(IList<char8_t>& in_chars)
    {
        _length = in_chars.size();
        _string_data = new char8_t[_length+1];
        for(size_t k = 0; k < _length; k++)
        {
            _string_data[k] = in_chars[k];
        }
        _string_data[_length] = 0;
    }

	String::String(const void* in_ptr)
    {
#ifdef _WIN64
        _string_data = new char8_t[19];
        _length = sprintf(_string_data, "0x%016lx", (unsigned long long)in_ptr);
#else
        _string_data = new char8_t[11];
        _length = sprintf(_string_data, "0x%08lx", (unsigned long)in_ptr);
#endif
    }

    size_t String::length() const
    {
        return _length;
    }

    String::operator const char8_t *() const
    {
        return _string_data;
    }

    bool String::equals(const String& in_string) const
    {
        if(this == &in_string)
        {
            return true;
        }
        else if(_length != in_string._length)
        {
            return false;
        }

        for(size_t k = 0; k <  _length; k++)
        {
            if(_string_data[k] - in_string._string_data[k])
            {
                return false;
            }
        }
        return true;
    }

    bool String::equals(const char8_t* left, const char8_t* right)
    {
        if(left == right)
        {
            return true;
        }
        else if(left == NULL || right == NULL)
        {
            return false;
        }

        while(*left && *right)
        {
            if(*left++ != *right++)
            {
                return false;
            }
        }

        if(*left || *right)
        {
            return false;
        }
        return true;
    }

    bool String::operator==(const char8_t* in_string) const
    {
        return String::equals(_string_data, in_string);
    }

    bool String::operator==(const String& in_string) const
    {
        return equals(in_string);
    }

    bool operator==(const char8_t* left, const String& right)
    {
        return String::equals(left, right._string_data);
    }

    bool String::operator!=(const char8_t* right) const
    {
        return String::equals(_string_data, right) == false;
    }

    bool String::operator!=(const String& in_string) const
    {
        if(_length != in_string._length)
        {
            return true;
        }
        if(this == &in_string)
        {
            return false;
        }

        return String::equals(_string_data, in_string._string_data) == false;
    }

    bool operator!=(const char8_t* left, const String& right)
    {
        return String::equals(left, right._string_data) == false;
    }

    String& String::operator=(const String& right)
    {
        if(_length != right._length)
        {
            delete[] _string_data;
            _length = right._length;
            _string_data = new char8_t[_length + 1];
            _string_data[_length] = 0;
        }

        memcpy(_string_data, right._string_data, _length);

        return *this;
    }

    String& String::operator=(const char8_t* right)
    {
        if(right == NULL)
        {
            delete[] _string_data;
            _length = 0;
            _string_data = new char8_t[1];
            _string_data[0] = 0;
            return *this;
        }

        int right_length = strlen(right);
        if(_length != right_length)
        {
            delete[] _string_data;
            _length = right_length;
            _string_data = new char8_t[_length + 1];
            _string_data[_length] = 0;
        }

        memcpy(_string_data, right, _length);

        return *this;
    }

	String& String::operator=(const IList<char8_t>& in_char_list)
	{
		_length = in_char_list.size();
		_string_data = new char8_t[_length+1];
		for(size_t k = 0; k < _length; k++)
		{
			_string_data[k] = in_char_list[k];
		}
		_string_data[_length] = '\0';

		return *this;
	}

    char8_t String::operator[] (size_t in_index) const
    {
        HHD_ASSERT(in_index >= 0 && in_index < _length);
        return _string_data[in_index];
    }

    void String::split(char c, Array1D<String>& out_strings) const
    {
        size_t start_index = 0;
        size_t substring_count = 0;

        for(size_t k = 0; k < _length; k++)
        {
            if(_string_data[k] == c)
            {
                substring_count++;
                start_index = k+1;
            }
        }

        if(start_index < _length)
        {
            substring_count++;
        }

		out_strings.setSize(substring_count);
        StringBuilder sb;

        size_t index = 0;
        for(size_t k = 0; k < _length; k++)
        {
            if(_string_data[k] == c)
            {
				sb >> out_strings(index++);
                sb.reset();
                start_index = k+1;
            }
            else
            {
                sb << _string_data[k];
            }
        }

        if(index < substring_count)
        {
			sb >> out_strings(index++);
        }
    }

    void String::split(const String& delimit, Array1D<String>& out_substrings) const
    {
        Vector< Pair< size_t, size_t > > substring_list;
        Pair<size_t, size_t> p(0, 0);

        for(size_t k = 0; k < _length; k++)
        {
            // see if we can find our string
            bool found_delimit = true;
            for(size_t j = 0; j < delimit._length; j++)
            {
                // not a match
                if(k+j == _length || _string_data[k+j] != delimit._string_data[j])
                {
                    found_delimit = false;
                    p.second++;
                    break;
                }
            }

            if(found_delimit)
            {
                if(k > 0)
                {
                    substring_list.add(p);
                }
                k += delimit._length;
                p.first = k;
                p.second = 1;
            }
        }

        if(p.first < _length)
        {
            substring_list.add(p);
        }

       out_substrings.setSize( substring_list.size() );

        for(size_t k = 0; k < substring_list.size(); k++)
        {
            Pair<size_t, size_t>& p = substring_list[k];
            out_substrings[k] = String(_string_data, p.first, p.second);
        }
    }

	bool String::startsWith( const char8_t* in_search ) const
	{
		if (in_search != NULL && _length > 0)
		{
			const char8_t* search_iter = in_search;
			const char8_t* internal_iter = _string_data;

			while (*internal_iter != '\0' && *search_iter != '\0')
			{
				if (*internal_iter != *search_iter)
				{
					return false;
				}
				else
				{
					internal_iter++;
					search_iter++;
				}
			}

			return *internal_iter == *search_iter ? true : false;
		}
		else
		{
			return false;
		}
	}

	const char8_t* String::whitespace()
	{
		static const char8_t whitespace_chars[] = " \t\n\r";
		return whitespace_chars;
	}

	size_t String::findCharacterIndex(const char8_t* in_character_set, size_t in_start_index) const
	{
		HHD_ASSERT(in_start_index < _length);
		HHD_ASSERT(in_character_set != NULL);
		HHD_ASSERT(*in_character_set != '\0');

		size_t index = in_start_index;
		const char8_t* intenal_ptr = &_string_data[index];
		const char8_t* character_set_ptr;

		do
		{
			character_set_ptr = in_character_set;
			do
			{
				if(*character_set_ptr == *intenal_ptr)
				{
					return index;
				}
			} while(*(character_set_ptr++) != '\0');
			index++;
		} while(*(intenal_ptr++) != '\0');

		return _length;
	}

	void String::substring( size_t in_start_index, size_t in_count, String& out_substring ) const
	{
		HHD_ASSERT(in_start_index < _length);
		HHD_ASSERT(in_start_index + in_count <= _length);

		if (out_substring._length < in_count + 1 )
		{
			// not enough memory, reallocate
			delete [] out_substring._string_data;
			out_substring._string_data = new char8_t[in_count+1];
			out_substring._length = in_count+1;
		} 

		strncpy(out_substring._string_data, &_string_data[in_start_index], in_count);
		out_substring._string_data[in_count] = '\0';
	}
}

// reenable the 4996 warning after this
#pragma warning( pop )
